package com.basker.pisces.domain.entity.data;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.common.LazyProvider;
import com.basker.pisces.core.meta.bean.BeanFieldDescriptor;
import com.basker.pisces.domain.annotation.ListInput;
import com.basker.pisces.domain.annotation.PropertyInput;
import com.basker.pisces.domain.constant.DomainConstants;
import com.basker.pisces.domain.entity.data.ListSearchHelper.KeywordSearchResult;
import com.basker.pisces.domain.entity.meta.AbstractEntity;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.InvalidPropertyValueException;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.PropertyCollection;
import com.basker.pisces.domain.property.meta.ReferenceProperty;
import com.basker.pisces.domain.property.meta.TextProperty;
import com.basker.pisces.service.exception.IObjectErrorsWrapper.ObjectError;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 输入POJO类的属性描述
 *
 * @author hangwen
 */
public class DataEntityInputBeanDescriptor {
    private Entity entity;
    private Class<?> inputBeanType;

//    private PropertyInputDescriptor identityPropertyInputDescriptor;

    private Map<Class<?>, Map<String, PropertyInputDescriptor>> typeGroupedPropertyInputDescriptors =
            new LinkedHashMap<>();
    private Map<Class<?>, PropertyInputDescriptor> typeGroupedIdentityPropertyInputDescriptor =
            new LinkedHashMap<>();
    //    private Map<String/* alias */, PropertyInputDescriptor> propertyInputDescriptors =
//            new TreeMap<>(new PropertyComparator());
    private Map<String/* alias */, ReferencePropertyInputValueCollector> referencePropertyInputCollectors;

    public DataEntityInputBeanDescriptor(Entity entity, Class<?> inputBeanType) {
        this.entity = entity;
        this.inputBeanType = inputBeanType;

        this.build(this.entity, this.inputBeanType);
    }

    public DataEntityInputBeanDescriptor(Entity entity, String childEntity, Class<?> inputBeanType) {
        this.entity = entity;
        this.inputBeanType = inputBeanType;

        this.build(this.entity.getChildEntity(childEntity), this.inputBeanType);
    }

    /**
     * 获取输入的标识属性值
     *
     * @param inputObject
     * @return
     */
    public Object getIdentityValue(Object inputObject) {
        if (inputObject == null) {
            return null;
        }

        return this.typeGroupedIdentityPropertyInputDescriptor.get(inputObject.getClass()).getValue(inputObject);
    }

    /**
     * 获取输入bean的类型
     *
     * @return
     */
    public Class<?> getInputBeanType() {
        return inputBeanType;
    }

    public Collection<PropertyInputDescriptor> getPropertyInputDescriptors(Class<?> inputBeanType) {
        return this.typeGroupedPropertyInputDescriptors.get(inputBeanType).values();
    }

//    public Collection<PropertyInputDescriptor> getPropertyInputDescriptors() {
//        return propertyInputDescriptors.values();
//    }

    public boolean hasIdentityPropertyInput(final Class<?> inputType) {
        return this.typeGroupedIdentityPropertyInputDescriptor.containsKey(inputType);
    }

    public void tryReferencePropertySearch(List<?> inputObjects) {
        if (CollectionUtils.isEmpty(inputObjects)) {
            return;
        }

        LazyProvider<List<ReferencePropertyInputValueCollector>> collectors =
                new LazyProvider<>(Lists::newArrayList);

        Class<?> inputType = inputObjects.get(0).getClass();
        Collection<PropertyInputDescriptor> propertyInputDescriptors =
                this.typeGroupedPropertyInputDescriptors.get(inputType).values();

        for (PropertyInputDescriptor propertyInputDescriptor : propertyInputDescriptors) {
            if (!(propertyInputDescriptor instanceof ReferencePropertyInputDescriptor)) {
                continue;
            }

            ReferencePropertyInputValueCollector collector =
                    this.collectInputValue((ReferencePropertyInputDescriptor) propertyInputDescriptor, inputObjects);
            if (collector != null) {
                collectors.get().add(collector);
            }
        }

        if (collectors.isPresent()) {
            for (ReferencePropertyInputValueCollector collector : collectors.get()) {
                this.doSearch(collector);
            }
        }
    }

    private void build(AbstractEntity entity, Class<?> inputBeanType) {
        // 获取POJO类的所有字段（为了与实体属性区分，这里的BeanFieldDescriptor成为字段）
        BeanFieldDescriptor[] beanFieldDescriptors = BeanFieldDescriptor.getBeanFieldDescriptors(inputBeanType);
        List<BeanFieldDescriptor> listFieldDescriptors = Lists.newArrayList();

        Map<String, PropertyInputDescriptor> propertyInputDescriptors =
                Maps.newTreeMap(new PropertyComparator(entity));
        this.typeGroupedPropertyInputDescriptors.put(inputBeanType, propertyInputDescriptors);

        for (BeanFieldDescriptor beanFieldDescriptor : beanFieldDescriptors) {
            //收集listInput 后续处理
            ListInput listInput = beanFieldDescriptor.getAnnotation(ListInput.class);
            if (listInput != null) {
                listFieldDescriptors.add(beanFieldDescriptor);
                continue;
            }

            // 获取PropertyInput注解，没有则忽略
            PropertyInput propertyInput = beanFieldDescriptor.getAnnotation(PropertyInput.class);
            if (propertyInput == null) {
                continue;
            }

            this.buildPropertyDescriptor(
                    entity,
                    inputBeanType,
                    beanFieldDescriptor,
                    propertyInput,
                    propertyInputDescriptors
            );
        }

        for (BeanFieldDescriptor listFieldDescriptor : listFieldDescriptors) {
            ListPropertyInputDescriptor listPropertyInputDescriptor = new ListPropertyInputDescriptor();
            listPropertyInputDescriptor.fieldDescriptor = listFieldDescriptor;

            ListInput listInput = listFieldDescriptor.getAnnotation(ListInput.class);
            String childEntity = listInput.childEntity();
            listPropertyInputDescriptor.childEntityName = childEntity;
            Class<?> itemType = listFieldDescriptor.getCollectionItemType();

            propertyInputDescriptors.put(childEntity, listPropertyInputDescriptor);
            this.build(this.entity.getChildEntity(childEntity), itemType);
        }
    }

    private PropertyInputDescriptor buildPropertyDescriptor(AbstractEntity entity,
                                                            Class<?> inputBeanType,
                                                            BeanFieldDescriptor beanFieldDescriptor,
                                                            PropertyInput propertyInput,
                                                            Map<String, PropertyInputDescriptor> propertyInputDescriptors) {
        // 获取字段对应的属性别名，进而获取属性对象
        String propertyName = getPropertyName(beanFieldDescriptor, propertyInput);
        Property<?> property = entity.getProperty(propertyName, true);
        boolean isRefProperty = property instanceof ReferenceProperty;

        // 对于同一个属性，可能会定义多个字段（引用属性可能存在对应其id的字段、检索字段），所以这里先从集合中尝试获取
        PropertyInputDescriptor propertyInputDescriptor = propertyInputDescriptors.get(propertyName);
        if (propertyInputDescriptor == null) {
            // 创建输入字段描述
            if (isRefProperty) {
                propertyInputDescriptor = new ReferencePropertyInputDescriptor(property);
            } else {
                propertyInputDescriptor = new PropertyInputDescriptor(property);
            }

            if (propertyInput.identity()) {
                PropertyInputDescriptor identityPropertyInputDescriptor =
                        this.typeGroupedIdentityPropertyInputDescriptor.get(inputBeanType);

                if (identityPropertyInputDescriptor != null) {
                    throw new IllegalStateException("Another identityPropertyInputDescriptor is exist:'"
                            + identityPropertyInputDescriptor.getDisplayName() + "'");
                }

                this.typeGroupedIdentityPropertyInputDescriptor.put(inputBeanType, propertyInputDescriptor);
            }

            propertyInputDescriptors.put(propertyName, propertyInputDescriptor);
        }

        if (isRefProperty) {
            // 对于引用属性，组装其代码和名称的字段
            ReferencePropertyInputDescriptor referenceProperyInputDescriptor =
                    (ReferencePropertyInputDescriptor) propertyInputDescriptor;

            boolean codeValue = propertyInput.codeValue();
            boolean nameValue = propertyInput.nameValue();
            if (codeValue || nameValue) {
                referenceProperyInputDescriptor.supportCodeSeach = codeValue;
                referenceProperyInputDescriptor.supportNameSeach = nameValue;

                referenceProperyInputDescriptor.searchFieldDescriptor = beanFieldDescriptor;

                return referenceProperyInputDescriptor;
            }
        }

        propertyInputDescriptor.fieldDescriptor = beanFieldDescriptor;
        propertyInputDescriptor.ignoreNull = propertyInput.ignoreNull();
        propertyInputDescriptor.ignoreInvalid = propertyInput.ignoreInvalid();

        return propertyInputDescriptor;
    }

    private void checkErrorInputs(ReferenceProperty referenceProperty, KeywordSearchResult result) {
        List<String> errorInputList = result.getErrorKeywords();
        if (!CollectionUtils.isEmpty(errorInputList)) {
            String errorMessage = "input value '" + errorInputList + "' is not found in " + referenceProperty + "";
            ObjectError error = new ObjectError(DomainConstants.ObjectErrorCode.OBJECT_CODE_INVALID, null, "",
                    errorMessage);
            throw new InvalidPropertyValueException(Arrays.asList(error));
        }
    }

    private ReferencePropertyInputValueCollector collectInputValue(ReferencePropertyInputDescriptor referencePropertyInputDescriptor,
                                                                   List<?> inputObjects) {
        if (!referencePropertyInputDescriptor.supportSearchInput()) {
            return null;
        }

        ReferencePropertyInputValueCollector collector = null;

        for (Object inputObject : inputObjects) {
            Object inputValue = referencePropertyInputDescriptor.searchFieldDescriptor.getValue(inputObject);

            if (!StringUtils.isEmpty(inputValue)) {
                if (collector == null) {
                    collector = new ReferencePropertyInputValueCollector(referencePropertyInputDescriptor);
                }

                collector.addInputValue((String) inputValue);
            }
        }

        if (collector != null) {
            Property<?> property = referencePropertyInputDescriptor.property;

            if (referencePropertyInputCollectors == null) {
                referencePropertyInputCollectors = new LinkedHashMap<>();
            }

            referencePropertyInputCollectors.put(property.getAlias(), collector);
        }

        return collector;
    }

    private void doSearch(ReferencePropertyInputValueCollector collector) throws InvalidPropertyValueException {
        if (collector.isEmpty()) {
            return;
        }

        ReferenceProperty referenceProperty = collector.property;
        // 没有引用实体,不支持代码和名称检索
        Entity searchEntity = referenceProperty.getReferenceEntity();
        if (searchEntity == null) {
            throw new RuntimeException(
                    "reference property " + referenceProperty.toString() + " has no reference entity!");
        }

        ListSearchHelper searchHelper = ListSearchHelper.create(referenceProperty);

        String[] searchProperties = collector.descriptor.getSearchProperties(searchEntity);
        String[] inputValues = collector.getInputValues();

        KeywordSearchResult result = searchHelper.searchByKeywords(searchProperties, inputValues);
        this.checkErrorInputs(referenceProperty, result);

        for (String inputValue : inputValues) {
            Object idValue = result.getIdValue(inputValue);
            collector.searchResult.put(inputValue, idValue);
        }
    }

    private String getPropertyName(BeanFieldDescriptor beanFieldDescriptor, PropertyInput propertyInput) {
        String name = propertyInput.propertyAlias();
        if (StringUtils.isEmpty(name)) {
            name = beanFieldDescriptor.getName();
        }

        return name;
    }

    /**
     * 搜索的结果
     *
     * @author hangwen
     */
    private static class SearchResult extends HashMap<String, Object> {

        private static final long serialVersionUID = 2726885748327197532L;

    }

    public static class PropertyInputDescriptor {

        protected Property<?> property;
        protected BeanFieldDescriptor fieldDescriptor;
        protected boolean ignoreNull = true;
        protected boolean ignoreInvalid = false;

        PropertyInputDescriptor(Property<?> property) {
            this.property = property;
        }

        public static boolean isList(final PropertyInputDescriptor propertyInputDescriptor) {
            return propertyInputDescriptor instanceof ListPropertyInputDescriptor;
        }

        public String getDisplayName() {
            return property.getDisplayName();
        }

        public Property<?> getProperty() {
            return this.property;
        }

        public void setValue(Object inputObject, Object inputValue) {
            this.fieldDescriptor.setValue(inputObject, inputValue, true);
        }

        @Override
        public String toString() {
            return this.property.toString();
        }

        Object getValue(Object inputObject) {
            return this.fieldDescriptor.getValue(inputObject);
        }

    }

    private static class PropertyComparator implements Comparator<String> {

        private AbstractEntity entity;

        public PropertyComparator(final AbstractEntity entity) {
            this.entity = entity;
        }

        @Override
        public int compare(String key1, String key2) {
            PropertyCollection properties = entity.getProperties();
            Property<?> p1 = entity.getProperty(key1);
            Property<?> p2 = entity.getProperty(key2);

            Integer idx1 = p1 != null ? properties.indexOf(p1) : Integer.MAX_VALUE;
            Integer idx2 = p2 != null ? properties.indexOf(p2) : Integer.MAX_VALUE;

            return idx1.compareTo(idx2);
        }

    }

    public class ListPropertyInputDescriptor extends PropertyInputDescriptor {

        String childEntityName;

        ListPropertyInputDescriptor() {
            super(null);
        }
    }

    public class ReferencePropertyInputDescriptor extends PropertyInputDescriptor {
        private BeanFieldDescriptor searchFieldDescriptor;
        private boolean supportCodeSeach = false;
        private boolean supportNameSeach = false;
        private Set<String> otherSearchProperties;

        ReferencePropertyInputDescriptor(Property<?> property) {
            super(property);
        }

        public void setSearchValue(Object inputObject, Object inputValue) {
            if (this.supportSearchInput()) {
                this.searchFieldDescriptor.setValue(inputObject, inputValue, true);
            }
        }

        /**
         * 是否支持检索值录入
         *
         * @return
         */
        public boolean supportSearchInput() {
            return supportCodeSeach || supportNameSeach || !CollectionUtils.isEmpty(otherSearchProperties);
        }

        void addOtherSearchProperty(String searchProperty) {
            if (this.otherSearchProperties == null) {
                this.otherSearchProperties = new LinkedHashSet<>();
            }
            this.otherSearchProperties.add(searchProperty);
        }

        String[] getSearchProperties(Entity searchEntity) {
            String[] ret = new String[calSearchPropertySize()];

            // 代码和名称属性
            TextProperty codeProperty = searchEntity.getCodeProperty();
            TextProperty nameProperty = searchEntity.getNameProperty();

            if (supportCodeSeach) {
                if (codeProperty == null) {
                    throw new RuntimeException("Entity " + searchEntity + " has no code property!");
                }
                ret[0] = codeProperty.getAlias();
            }

            if (supportNameSeach) {
                if (nameProperty == null) {
                    throw new RuntimeException("Entity " + searchEntity + " has no name property!");
                }
                ret[1] = nameProperty.getAlias();
            }

            if (otherSearchProperties != null) {
                int nextIndex = 2;
                Iterator<String> iterator = otherSearchProperties.iterator();
                while (iterator.hasNext()) {
                    ret[nextIndex++] = iterator.next();
                }
            }

            return ret;
        }

        @Override
        Object getValue(Object inputObject) {
            // 优先获取id
            if (this.fieldDescriptor != null) {
                Object idValue = super.getValue(inputObject);
                if (idValue != null) {
                    return idValue;
                }
            }

            if (!this.supportSearchInput() || referencePropertyInputCollectors == null) {
                return null;
            }

            ReferencePropertyInputValueCollector collector = referencePropertyInputCollectors
                    .get(this.property.getAlias());
            if (collector == null) {
                return null;
            }

            Object searchValue = this.searchFieldDescriptor.getValue(inputObject);
            if (StringUtils.isEmpty(searchValue)) {
                return null;
            }

            return collector.searchResult.get(searchValue);
        }

        private int calSearchPropertySize() {
            int len = otherSearchProperties != null ? otherSearchProperties.size() : 0;
            if (supportCodeSeach) {
                len++;
            }
            if (supportNameSeach) {
                len++;
            }

            return len;
        }
    }

    /**
     * 记录一个引用属性录入值的收集器
     *
     * @author hangwen
     */
    private class ReferencePropertyInputValueCollector {

        private ReferencePropertyInputDescriptor descriptor;
        private ReferenceProperty property;
        private SearchResult searchResult = new SearchResult();

        ReferencePropertyInputValueCollector(ReferencePropertyInputDescriptor descriptor) {
            this.descriptor = descriptor;
            this.property = (ReferenceProperty) descriptor.property;
        }

        void addInputValue(String inputValue) {
            if (searchResult.containsKey(inputValue)) {
                return;
            }

            searchResult.put(inputValue, null);
        }

        String[] getInputValues() {
            return searchResult.keySet().toArray(new String[searchResult.size()]);
        }

        boolean isEmpty() {
            return searchResult.isEmpty();
        }

    }
}
